package com.nine.framework.config;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置
 *
 * @author ruoyi
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        // 调用afterPropertiesSet方法来完成RedisTemplate的初始化过程，这是Spring框架中InitializingBean接口的一部分，用于在bean的所有属性设置完毕后执行自定义的初始化逻辑。
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public DefaultRedisScript<Long> limitScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(limitScriptText());
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 限流脚本
     */
    private String limitScriptText() {
        // key: 从KEYS数组中获取的Redis key，用于标识需要进行限流的资源。
        return "local key = KEYS[1]\n"
                // count: 从ARGV数组中获取的允许的最大访问次数。
                + "local count = tonumber(ARGV[1])\n"
                // time: 从ARGV数组中获取的限流时间窗口（通常以秒为单位）。
                + "local time = tonumber(ARGV[2])\n"
                // current: 当前key对应的访问计数
                + "local current = redis.call('get', key);\n"
                // 如果current存在且其数值大于count，则返回当前的计数，表示已达到限流上限。
                + "if current and tonumber(current) > count then return tonumber(current); end\n"
                // 如果未达到限流上限，则使用redis.call('incr', key)将key对应的计数增加1。
                + "current = redis.call('incr', key)\n"
                // 如果增加后的计数为1（即这是该key的第一次访问），则使用redis.call('expire', key, time)为key设置过期时间，确保在指定的时间窗口后计数会自动重置。
                + "if tonumber(current) == 1 then redis.call('expire', key, time) end"
                // 返回增加后的计数。
                + " return tonumber(current);";
    }
}
